/**
 * Progetto PRAISED.
 * 13/ott/2010
 */
package it.uniroma3.dia.praised.brenda.crawler;

import it.uniroma3.dia.praised.brenda.model.Enzyme;
import it.uniroma3.dia.praised.brenda.model.Literature;
import it.uniroma3.dia.praised.brenda.model.Synonym;
import it.uniroma3.dia.praised.brenda.validator.Validator;
import it.uniroma3.dia.praised.brenda.wrapper.WrapperBrenda;
import it.uniroma3.dia.praised.brenda.wrapper.WrapperPubMed;
import it.uniroma3.dia.praised.exception.CrawlerException;
import it.uniroma3.dia.praised.exception.FactoryException;
import it.uniroma3.dia.praised.factory.WebClientFactory;
import it.uniroma3.dia.praised.util.SerializeXML;
import it.uniroma3.dia.praised.util.WebPageExtractionResult;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;

import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

/**
 * CrawlerBrendaWorker : Classe responsabile della creazione dell'Enzima.
 * Inoltre si prende il carico di esplorare/crawlare tutti gli URL dai quali
 * si possono ottenere informazioni da poter aggiungere all'Enzima stesso.
 * 
 * Questa classe gestisce un Thread per ogni URL.
 *  
 * @author Accosta Augusto
 * @author Lopez Elisabetta
 * @see it.uniroma3.dia.praised.web.WebPageEnzima
 * @version 1.0
 *
 */
public class CrawlerBrendaWorker implements Callable<Enzyme> {

	private static final ConcurrentMap<String, Literature> cacheLetterature = new ConcurrentHashMap<String, Literature>();
	
	private URL url;
	private ConcurrentLinkedQueue<URL> badList;
	
	private final WrapperPubMed wrapperPubMed;
	private final WrapperBrenda wrapperBrenda;
	
	/**
	 * Costruttore che inizializza la Factory ed i Wrapper
	 * 
	 * @param part - porzione di lista che il thread deve gestire 
	 * @param threadID - identificatore del thread
	 * @throws CrawlerException 
	 * 
	 */
	public CrawlerBrendaWorker(URL url, ConcurrentLinkedQueue<URL> badList) throws CrawlerException {
		this.url = url;
		this.badList = badList;

		this.wrapperBrenda = new WrapperBrenda();
		this.wrapperPubMed = new WrapperPubMed();
		
	}

	/**
	 * Metodo che definisce il comportamento del singolo thread nel momento in cui questo viene chiamato.
	 * Viene creato un Enzima per ogni Thread. 
	 * 
	 * @return processedUrlMap - mappa contenente url - risultato dell'elaborazione
	 */
	@Override
	public Enzyme call() {
		Enzyme enzyme = new Enzyme();
		WebPageExtractionResult webPageInfoExtraction = new WebPageExtractionResult(false);
		
		try {
			
			URL detailedURL = getDetailedURL();
			if(detailedURL == null) { // Extraction URL problems
				this.badList.add(this.url);
				return null;
			}
			
			long start = System.currentTimeMillis();
			this.wrapperBrenda.wrapPageIntoEnzyme(enzyme, detailedURL, webPageInfoExtraction); // Tecnica del Side-Effect
			long end = System.currentTimeMillis();
			
			if(!webPageInfoExtraction.getWebPageExtractionResult()) { /* Information extraction problems */
				this.badList.add(this.url);
				return null;
			}
			
			/* Estrazione letterature */
			this.pubMedIdExtraction(enzyme);
			
			/* Salvo un file xml per ogni enzima */
			SerializeXML save = new SerializeXML(enzyme);
			save.serializeEnzyme();
			
			// Vedo quanto ci mette una pagina ad essere processata
			System.err.println("PROCESSED: " + enzyme.getID() + " (" + enzyme.getSynonyms().size() + ")" + " TIME: " + (end - start));
		} catch (Exception e) {
			return null;
		}
		
		return enzyme;
	}

	/** Metodo che estrae il PubMed id relativo ad ognuno dei sinonimi dell'enzima
	 * @param enzyme
	 * @throws MalformedURLException
	 */
	private void pubMedIdExtraction(Enzyme enzyme) throws MalformedURLException {
		
		for (Synonym synonym : enzyme.getSynonyms()) {
			
			/* A partire dalla mappa brendaId -> pubmedId creata in precedenza, per ogni chiave (brendaId) 
			 * si cerca la corrispondente letteratura sul sito PubMed */
			for(Map.Entry<String, Literature> brendaIdToLiterature : synonym.getLiterature().entrySet()) { // scorro la mappa
				String brendaID = brendaIdToLiterature.getKey();
				
				/* La letteratura viene aggiunta alla mappa solo se assente */
				Literature literature;
				if(cacheLetterature.containsKey(brendaID)) {
					literature = cacheLetterature.get(brendaID);
				} else {
					String brendaPubMedURL = buildBrendaPubMedURL(enzyme.getID(), brendaID);
					literature = new Literature();
					this.wrapperPubMed.wrapPageIntoLiterature(literature, brendaID, new URL(brendaPubMedURL)); // Side-Effect
					cacheLetterature.putIfAbsent(brendaID, literature);
				}
				
				brendaIdToLiterature.setValue(literature);
			}
		}
		
		
	}
	
	/**
	 * Metodo che esplora la pagina assegnatagli alla creazione dell'istanza del CrawlerBrendaWorker.
	 * Va a cercare nella pagina in questione l'URL che porta alla pagina da cui realmente posso
	 * estrarre tutte le informazioni relative all'Enzima.
	 *  
	 * @return Il link del sito che contiene le info da estrarre.
	 * @see HtmlUnit
	 * @throws CrawlerException
	 * @throws FactoryException 
	 */
	private URL getDetailedURL() {
		
		try {
			HtmlPage page = WebClientFactory.newHtmlPage(this.url);
		
			HtmlElement body = page.getBody();
			List<HtmlElement> bodyelements = body.getElementsByTagName("a");
			
			/* Prendo il primo elemento della lista di ancore che contiene l'url necessario per la navigazione */
			String urlDaNavigare = bodyelements.get(1).getAttribute("href");
								
			urlDaNavigare = urlDaNavigare.replace("result_flat", "flat_result");
			String urlEffettiva = urlDaNavigare.substring(2) + "&organism_list=&Suchword=&UniProtAcc=";
							
			return Validator.buildURL(url, urlEffettiva);
			
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * Metodo che costruisce staticamente l'URL relativo alle letterature di Brenda.
	 * 
	 * @param idEnzima
	 * @param brendaID
	 * @return
	 */
	private String buildBrendaPubMedURL(String idEnzima, String brendaID) {
		return "http://www.brenda-enzymes.org/literature/lit.php4?e=" + idEnzima + "&r=" + brendaID;
	}

}
